#pragma once
#include "ViewBase.h"

template <class T>
class CChildViewAnimate
{
public:
	enum ViewAnimation
	{
		None,				// Don't animate the child transitions
		ScrollLeft,			// Scroll the views to the left
		ScrollRight,		// Scroll the views to the right
		ScrollUp,			// Scroll the views up
		ScrollDown			// Scroll the views down
	};


protected:
	CViewBase*		m_pView;		// Current child view
	DWORD			m_dwMaxTick;	// Maximum number of ticks to wait for when scrolling

	// ScrollWait:
	//
	//		Waits a fixed number of milliseconds between two scrolls
	//
	void ScrollWait(DWORD dwTickIni, DWORD dwTickEnd)
	{
		DWORD dwTickDel = dwTickEnd - dwTickIni;

		if(dwTickDel > m_dwMaxTick)
		{
			m_dwMaxTick = dwTickDel;
		}
		else
		{
			// Preserve original timing (yes, this burns the CPU)
			while(GetTickCount() - dwTickIni < m_dwMaxTick)
				;
		}
	}

	// ScrollHorizontal
	//
	//		Horizontally scrolls the view
	//
	void ScrollHorizontal(CDC& dcCli, CRect& rc, CWindow wndNewView, ViewAnimation nAnimation, int dx)
	{
		int		xScroll = nAnimation == ScrollLeft ? -dx : dx,
				x,
				nWidth	= rc.Width(),
				nHeight	= rc.Height();
		CRect	rcInvalid(rc);
		DWORD	dwTickIni,
				dwTickEnd;

		// Calculate the ivalid rect
		if(nAnimation == ScrollLeft)
			rcInvalid.left = rc.Width() - dx;
		else
			rcInvalid.right = dx;

		for(x = dx; x <= rc.Width(); x += dx)
		{
			dwTickIni = GetTickCount();
			dcCli.ScrollDC(xScroll, 0, &rc, &rc, NULL, NULL);
			dwTickEnd = GetTickCount();

			ScrollWait(dwTickIni, dwTickEnd);

			if(nAnimation == ScrollLeft)
				wndNewView.MoveWindow(nWidth - x, rc.top, nWidth, nHeight, FALSE);
			else
				wndNewView.MoveWindow(-nWidth + x, rc.top, nWidth, nHeight, FALSE);

			wndNewView.InvalidateRect(&rcInvalid, TRUE);
			wndNewView.UpdateWindow();
		}
	}

	// ScrollVertical
	//
	//		Vertically scrolls the view
	//
	void ScrollVertical(CDC& dcCli, CRect& rc, CWindow wndNewView, ViewAnimation nAnimation, int dy)
	{
		int		yScroll = nAnimation == ScrollUp ? -dy : dy,
				y,
				nWidth	= rc.Width(),
				nHeight	= rc.Height();
		CRect	rcInvalid(rc);
		DWORD	dwTickIni,
				dwTickEnd;

		// Calculate the ivalid rect
		if(nAnimation == ScrollUp)
			rcInvalid.top = nHeight - dy;
		else
			rcInvalid.bottom = rc.top + dy;

		for(y = dy; y <= nHeight; y += dy)
		{
			dwTickIni = GetTickCount();
			dcCli.ScrollDC(0, yScroll, &rc, &rc, NULL, NULL);
			dwTickEnd = GetTickCount();

			ScrollWait(dwTickIni, dwTickEnd);

			if(nAnimation == ScrollUp)
				wndNewView.MoveWindow(0, rc.bottom - y, nWidth, nHeight, FALSE);
			else
				wndNewView.MoveWindow(0, rc.top - nHeight + y, nWidth, nHeight, FALSE);

			wndNewView.InvalidateRect(&rcInvalid, FALSE);
			wndNewView.UpdateWindow();
		}
	}


	// AnimateView:
	//
	//		Animates the child view transitioning
	//
	void AnimateView(CWindow wndNewView, ViewAnimation nAnimation)
	{
		T*			pT = static_cast<T*>(this);
		int			dx = 16,
					dy = 16;
		CDC			dcMem;
		CClientDC	dc(pT->m_hWnd);
		CBitmap		bmp;
		CRect		rc;
		HDC			hClientDC;
		CWindow		wndOldView(pT->m_hWndClient);

		// Repaint the current view
		wndOldView.Invalidate(FALSE);
		wndOldView.UpdateWindow();

		hClientDC = pT->GetDC();

		//pT->GetClientRect(&rc);
		wndOldView.GetWindowRect(&rc);
		pT->ScreenToClient(&rc);

		// Create a bitmap snapshot of the old view
		dcMem.CreateCompatibleDC();
		bmp.CreateCompatibleBitmap(dcMem, rc.Width(), rc.Height());
		dcMem.SelectBitmap(bmp);
		dcMem.BitBlt(0, 0, rc.Width(), rc.Height(), hClientDC, 0, 0, SRCCOPY);

		pT->ReleaseDC(hClientDC);

		wndNewView.MoveWindow(rc.Width(), rc.top, rc.Width(), rc.Height(), FALSE);
		wndNewView.ShowWindow(SW_SHOW);

		dc.BitBlt(0, rc.top, rc.Width(), rc.Height(), dcMem, 0, 0, SRCCOPY);

		wndOldView.ShowWindow(SW_HIDE);
		m_dwMaxTick = 0;

		switch(nAnimation)
		{
			case ScrollLeft:
			case ScrollRight:
				ScrollHorizontal(dc, rc, wndNewView, nAnimation, dx);
				break;

			case ScrollUp:
			case ScrollDown:
				ScrollVertical(dc, rc, wndNewView, nAnimation, dy);
				break;
		}
	}

public:
	CChildViewAnimate()
	:	m_pView		(NULL),
		m_dwMaxTick	(0)
	{
	}

	// ChildPreTranslateMessage
	//
	//		PreTranslateMessage handler.
	//		Call this from the main form.
	//
	BOOL ChildPreTranslateMessage(MSG *pMsg)
	{
		if(m_pView != NULL)
			return m_pView->PreTranslateMessage(pMsg);
		return FALSE;
	}

	// SwitchToView
	//
	//		Switches the current view to a new one.
	//		Returns the old view or NULL if switching to the same view.
	//
	CViewBase* SwitchToView(CViewBase *pView, ViewAnimation nAnimation)
	{
		CWindow		wndOldView	(m_pView->GetHWND()),
					wndNewView	(pView->GetHWND());
		CViewBase*	pOldView	(m_pView);

		// Don't change to self.
		if(pView == m_pView)
			return NULL;

		T* pT = static_cast<T*>(this);

		if(nAnimation != None)
			AnimateView(wndNewView, nAnimation);

		// Perform the child view switch
		pT->m_hWndClient = wndNewView;

		wndOldView.ShowWindow(SW_HIDE);
		wndOldView.SetWindowLongPtr(GWL_ID, 0);

		m_pView = pView;

		wndNewView.SetWindowLongPtr(GWL_ID, ATL_IDW_PANE_FIRST);
		wndNewView.SetFocus();

		pT->UpdateLayout();

		// If we did not animate the new child view, make
		// sure it is visible and updated.
		if(nAnimation == None)
		{
			wndNewView.ShowWindow(SW_SHOW);
			wndNewView.Invalidate(FALSE);
			wndNewView.UpdateWindow();
		}

		return pOldView;
	}

};
